package org.jlambda.util;

import java.util.Iterator;
import java.util.NoSuchElementException;

import org.jlambda.functions.Fun1;
import org.jlambda.functions.Fun2;

public class Nodes {

	public static class Node<T> implements Iterable<T>{
		protected Node<T> next, prev;
		protected T val;
		
		public Node(T val){
			this.val = val;
		}
		
		private class ValItr implements Iterator<T> {

			Node<T> current = new Node<T>(null);
			private ValItr(Node<T> from){
				current.next = from;
			}
			
			public boolean hasNext() {
				return (current.next != null);
			}

			public T next() {
				if (current.next == null) {
					throw new NoSuchElementException("No more elements in nodes");
				}
				current = current.next;
				return current.val;
			}

			public void remove() {
			}
			
		}
		
		public Iterator<T> iterator(){
			return new ValItr(this);
		}
	}

	
	private static class ReverseValItr<T> implements Iterator<T> {

		Node<T> current = new Node<T>(null);
		private ReverseValItr(Node<T> from){
			current.prev = from;
		}
		
		public boolean hasNext() {
			return (current.prev != null);
		}

		public T next() {
			if (current.prev == null) {
				throw new NoSuchElementException("No more elements in nodes");
			}
			current = current.prev;
			return current.val;
		}

		public void remove() {
		}
		
	}
	public static class ReverseIterableNode<T> implements Iterable<T>{
		Node<T> node = null;
		public ReverseIterableNode(Node<T> node){
			this.node = node;
		}
		
		public Iterator<T> iterator(){
			return new ReverseValItr(node);
		}
	}

	
	public static final <T> Node<T> node(T val){
		return new Node<T>(val);
	}
	
	/**
	 * Modifies the current node to point to next. Single only
	 * 
	 * @param <T>
	 * @param current
	 * @param next
	 * @return
	 */
	public static final <T> Node<T> joinNext(Node<T> current, Node<T> next){
		current.next = next;
		return current;
	}
	
	public static final class NodeLength<T> extends Fun2< Fun1<Node<T>, Number> , Node<T>, Number> {
		@Override
		public Number apply(Fun1<Node<T>, Number> f, Node<T> node) {
			if (node.next == null) {
				return 1;
			}
			return 1 + f.apply(node.next).longValue();
		}
	}
	
	public static final class NodeValue<T> extends Fun1<Node<T>, T> {
	@Override
		public T apply(Node<T> p1) {
			return p1.val;
		}
	}
}
